#! /usr/bin/perl
# (Tested with -w; 10/5/04)
#
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License.
#
# Find the parse.sub routine.
my $maintdir = "maint";
my $rootdir  = ".";
if ( ! -s "maint/parse.sub" ) {
    my $program = $0;
    $program =~ s/extracterrmsgs//;
    if (-s "$program/parse.sub") {
        $maintdir = $program;
        $rootdir  = $program;
        $rootdir  =~ s/\/maint\///g;
        $rootdir  =~ s/\\maint\\//g;
        print "Rootdir = $rootdir\n" if $debug;
    }
}
require "$maintdir/parse.sub";

$debug = 0;
$careful = 0;        # Set careful to 1 to flag unused messages
$carefulFilename = "";
$showfiles = 0;
$quiet = 0;
$build_test_pgm = 1;

# Strict is used to control checking of error message strings.
$gStrict = 0;
if (defined($ENV{"DEBUG_STRICT"})) { $gStrict = 1; }

# Check for special args
@files = ();
%skipFiles = ();
$outfile = "";
$testfile = "errtest.c";
$outpath = ".";
$srcroot = $rootdir;

foreach $arg (@ARGV) {
    if ($arg =~ /^-showfiles/) { $showfiles = 1; }
    elsif( $arg =~ /-debug/) { $debug = 1; }
    elsif( $arg =~ /-quiet/) { $quiet = 1; }
    elsif( $arg =~ /-notest/) { $build_test_pgm = 0; }
    elsif( $arg =~ /-outfile=(.*)/) { $outfile = $1; }
    elsif( $arg =~ /-outpath=(.*)/) { $outpath = $1; }
    elsif( $arg =~ /-testfile=(.*)/) { $testfile = $1; }
    elsif( $arg =~ /-srcroot=(.*)/) { $srcroot = $1; }
    elsif( $arg =~ /-careful=(.*)/) {
        $careful = 1;
        $carefulFilename = $1;
    }
    elsif( $arg =~ /-careful/) { $careful = 1; }
    elsif( $arg =~ /-strict/)  { $gStrict = 1; }
    elsif( $arg =~ /-skip=(.*)/) { $skipFiles{$1} = 1; }
    else {
        print "Adding $arg to files\n" if $debug;
        if (-d $arg) {
            # Add all .c files from directory $arg to the list of files
            # to process (this lets us shorten the arg list)
            @files = (@files, &ExpandDir( $arg ));
        }
        else {
            $files[$#files+1] = $arg;
        }
    }
}
# End of argument processing


# Setup the basic file for errnames - Now determined in ExpandDirs
#@errnameFiles = ( "$srcroot/errnames.txt" );

if ($outfile ne "") {
    print STDOUT "Creating out file $outpath\\$outfile\n";
    $OUTFD = "MyOutFile";
    open( $OUTFD, ">$outpath\\$outfile" ) or die "Could not open $outpath\\$outfile\n";
}
else {
    $OUTFD = STDOUT;
}
# Setup before processing the files
if ($build_test_pgm) {
    print STDOUT "Creating test file $outpath\\$testfile\n";
    open( TESTFD, ">$outpath\\$testfile" ) or die "Cannot create test program $outpath\\$testfile\n";
    print TESTFD "/* -*- Mode: C++; c-basic-offset:4 ; -*- */\
/*  \
 *  (C) 2004 by Argonne National Laboratory.\
 *      See COPYRIGHT in top-level directory.\
 *\
 * This file is automatically generated by maint/extracterrmsgs\
 * DO NOT EDIT\
 */\n";
    print TESTFD "
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <oacr.h>
#include <windows.h>
#include \"mpi.h\"
#include \"mpierror.h\"
#include \"errcodes.h\"

#pragma warning(disable:4100) // unreferenced formal parameter

void MPID_Type_get_envelope(MPI_Datatype datatype, int* num_integers, int* num_addresses, int* num_datatypes, int* combiner)
{
    *combiner = MPI_COMBINER_NAMED;
}

typedef struct MPID_Comm MPID_Comm;

_Analysis_noreturn_
int
MPID_Abort(
    _Inout_opt_ MPID_Comm* comm,
    _In_ BOOL intern,
    _In_ int exit_code,
    _In_z_ const char* error_msg
    )
{
    printf(\"MPID_Abort called. exit code (%d); msg '%s'\", exit_code, error_msg);
    exit(exit_code);
}

_Success_(return>=0)
int 
MPIU_Internal_error_printf(
    _In_z_ _Printf_format_string_params_(...) const char *str, 
    ...
    )
{
    int n;
    va_list list;

    va_start(list, str);
    n = vfprintf(stderr, str, list);
    va_end(list);

    fflush(stderr);

    return n;
}

void ChkMsg( int err, int msgclass, const char msg[] )
{
    char errmsg[MPI_MAX_ERROR_STRING];

    MPIR_Err_get_string( err, errmsg, MPI_MAX_ERROR_STRING );

    printf( \"[0x%08x] [0x%08x] %2d %s\\n%s\\n\", err, MPIR_Err_get_user_error_code(err), msgclass, msg, errmsg );
}
\n\n";

    print TESTFD "int __cdecl main(int argc, char **argv)\n";
    print TESTFD "{\n    int err;\n\n";
    print TESTFD "    printf(\"mpi_errno    user_errno  class  error id string\\n\");\n";
}

# Process the definitions
foreach $file (@files) {
    print "$file\n" if $showfiles;
    &ProcessFile( $file );
}

#
# Create the hash %longnames that maps the short names to the long names,
# $longnames{shortname} => longname, by reading the errnames.txt files
foreach my $sourcefile (@errnameFiles) {
    #print STDERR "processing $sourcefile for error names\n";
    &ReadErrnamesFile( $sourcefile );
}

# Create the output files from the input that we've read
&CreateErrmsgsHeader( $OUTFD );
&CreateErrMsgMapping( $OUTFD );

if ($build_test_pgm) {
    print TESTFD "    printf(\"---------- end ----------\\n\");\n";
    print TESTFD "\n    return 0;\n}\n";
    close TESTFD;
}

#
# Generate a list of unused keys
if ($careful) {
    my $OUTFD = STDERR;
    if ($carefulFilename ne "") {
        $OUTFD = "ERRFD";
        open $OUTFD, ">$carefulFilename" or die "Cannot open $carefulFilename";
    }
    foreach $shortname (keys(%longnames)) {
        if (!defined($longnamesUsed{$shortname}) ||
            $longnamesUsed{$shortname} < 1) {
            $loc = $longnamesDefined{$shortname};
            print $OUTFD "Name $shortname is defined in $loc but never used\n";
        }
    }
    if ($carefulFilename ne "") {
        close $OUTFD;
    }
}

#-----------------------------------------------------------------------------
# ROUTINES
# ----------------------------------------------------------------------------
# From the data collected above, generate the file containing the error message
# text.
# This is a temporary routine; the exact output form will be defined later
sub CreateErrmsgsHeader {
    $FD = $_[0];
    print $FD "/* -*- Mode: C; c-basic-offset:4 ; -*- */\
/*  \
 *  (C) 2001 by Argonne National Laboratory.\
 *      See COPYRIGHT in top-level directory.\
 *\
 * This file automatically created by extracterrmsgs\
 * DO NOT EDIT\
 */\n";
    print $FD "typedef struct msgpair {\
    const char* key;
    const char* fmt;
} msgpair;\n\n"
}
#
# We also need a way to create the records
# We then hash these on the first occurance (or precompute the hashes?)
#
# The error messages are output in the following form:
# typedef struct {const char short[], const long[]} namemap;
# Generic messages
# static const char[] short1 = "";
# static const char[] long1 = "";
# ...
# static const namemap[] = { {short1, long1}, {...} }
#
sub CreateErrMsgMapping {
    my $OUTFD = $_[0];

    # Create a mapping of MPI error classes to the specific error
    # message by index into generic_err_msgs.  This reads the file
    # baseerrnames, looks up the generic message, and maps the MPI error
    # class to the corresponding index.
    # We must do this here because we must ensure that all MPI error
    # classes have been added to the generic messages
    @class_msgs = ();
    open (FD, "<$srcroot/baseerrnames.txt" ) ||
        die "Could not open $srcroot/baseerrnames.txt\n";
    while (<FD>) {
        s/#.*$//;
        my ($mpiname,$num,$shortmsg) = split(/\s\s*/);
        if (!defined($shortmsg)) {
            # Incase there is no short message entry (!)
            $shortmsg = "";
        }
        if ($shortmsg ne "")
        {
            if ($shortmsg =~ /\%/)
            {
                print STDERR "$srcroot/baseerrnames.txt(1) : error : message $shortmsg in baseerrnames.txt contains format control\n";
            }

            $specific_msgs{$shortmsg}++;
            $specific_loc{$shortmsg} = ":baseerrnames.txt(1)";

            $class_msgs[$num] = "$shortmsg";
        }
    }
    close (FD);

    $num = 0;
    # Now output the instance specific messages
    foreach $key (sort keys %specific_msgs)
    {
        $longvalue = "\"\0\"";

        if (!defined($longnames{$key}))
        {
            print STDERR "$specific_loc{$key} : error : shortname $key for specific messages has no expansion\n";
            next;
        }
        else {
            # Keep track of which messages we have seen
            $longnamesUsed{$key} += 1;
        }

        # Escape any naked quotes
        $longvalue =~ s/(?<!\\)\"/\\\"/;
        $longvalue = "\"" . $longnames{$key} . "\"";

        print $OUTFD "extern const __declspec(selectany) char short_spc$num\[\] = \"$key\";\n";
#       print $OUTFD "static const char short_spc$num\[\] = $key;\n";
        print $OUTFD "extern const __declspec(selectany) char long_spc$num\[\]  = $longvalue;\n";
        $short_to_num{$key} = $num;
        $num ++;
    }
    # Generate the mapping of short to long names

    print $OUTFD "\n\nextern const __declspec(selectany) msgpair errors_map[] = {\n";
    for (my $i = 0; $i < $num ; $i ++) {
        print $OUTFD "{ short_spc$i, long_spc$i }";
        print $OUTFD "," if ($i < $num - 1);
        print $OUTFD "\n";
    }
    print $OUTFD "};\n\n";

    print $OUTFD "extern const __declspec(selectany) int class_to_index[] = {\n";
    for (my $i=0; $i<=$#class_msgs; $i++) {
        print $OUTFD "$short_to_num{$class_msgs[$i]}";
        print $OUTFD "," if ($i < $#class_msgs);
        print $OUTFD "\n" if !(($i + 1) % 10);
    }
    print $OUTFD "};\n";
}
#
# Add a call to test this message for the error message.
# Handle both the generic and specific messages
#
sub AddTestCall {

    my $genericArgLoc = $_[0];

    my $last_errcode = "MPI_SUCCESS";  # $_[0];
    my $fatal_flag   = "MPIR_ERR_RECOVERABLE"; # $_[1];
    my $fcname       = "unknown"; # $_[2];
    my $linenum      = "__LINE__"; # $_[3];
    my $errclass     = "MPI_ERR_OTHER"; # $_[4];

    my $specific_msg = $_[$genericArgLoc+1];
    if ($specific_msg =~ /(\".*\")/)
    {
        $specific_msg = $1;
    }

    # Ensure that the last_errcode, class and fatal flag are specified.  There are a few places where these are variables.
    if (!($last_errcode =~ /MPI_ERR_/) )
    {
        $last_errcode = "MPI_SUCCESS";
    }
    if (!($errclass =~ /MPI_ERR_/) )
    {
        $errclass = "MPI_ERR_OTHER";
    }
    if (!($fatal_flag =~ /MPIR_ERR_FATAL/) && !($fatal_flag =~ /MPIR_ERR_RECOVERABLE/))
    {
        $fatal_flag = "MPIR_ERR_FATAL";
    }

    # Specific messages
    if (!defined($test_specific_msg{$specific_msg}))
    {
        $test_specific_msg{$specific_msg} = $filename;

        print TESTFD "    {\n";
        print TESTFD "    /* $filename */\n";
        # Use types in the string to create the types with default
        # values
        my $format = $specific_msg;
        my $fullformat = $format;
        my $narg = 0;
        my @args = ();
        while ($format =~ /[^%]*%(.)(.*)/)
        {
            my $type = $1;
            $format  = $2;
            $narg ++;
            if ($type eq "x")
            {
                print TESTFD "    int i$narg = $narg;\n";
                $args[$#args+1] = "i$narg";
            }
            elsif ($type eq "d")
            {
                print TESTFD "    int i$narg = $narg;\n";
                $args[$#args+1] = "i$narg";
            }
            elsif ($type eq "i")
            {
                print TESTFD "    int i$narg = $narg;\n";
                $args[$#args+1] = "i$narg";
            }
            elsif ($type eq "t")
            {
                print TESTFD "    int i$narg = $narg;\n";
                $args[$#args+1] = "i$narg";
            }
            elsif ($type eq "s")
            {
                print TESTFD "    char s$narg\[\] = \"string$narg\";\n";
                $args[$#args+1] = "s$narg";
            }
            elsif ($type eq "p")
            {
                print TESTFD "    char s$narg\[\] = \"pointer$narg\";\n";
                $args[$#args+1] = "s$narg";
            }
            elsif ($type eq "C")
            {
                print TESTFD "    int i$narg = MPI_COMM_WORLD;\n";
                $args[$#args+1] = "i$narg";
            }
            elsif ($type eq "I")
            {
                print TESTFD "    int i$narg = MPI_INFO_NULL;\n";
                $args[$#args+1] = "i$narg";
            }
            elsif ($type eq "D")
            {
                print TESTFD "    int i$narg = MPI_INT;\n";
                $args[$#args+1] = "i$narg";
            }
            elsif ($type eq "F")
            {
                # This must be an MPI_File since that type may not
                # be an integer (it is a pointer at this time)
                print TESTFD "    MPI_File i$narg = MPI_FILE_NULL;\n    OACR_USE_PTR( i$narg );\n";
                $args[$#args+1] = "i$narg";
            }
            elsif ($type eq "W")
            {
                print TESTFD "    int i$narg = MPI_WIN_NULL;\n";
                $args[$#args+1] = "i$narg";
            }
            elsif ($type eq "A")
            {
                print TESTFD "    int i$narg = $narg;\n";
                $args[$#args+1] = "i$narg";
            }
            elsif ($type eq "G")
            {
                print TESTFD "    int i$narg = MPI_GROUP_NULL;\n";
                $args[$#args+1] = "i$narg";
            }
            elsif ($type eq "O")
            {
                print TESTFD "    int i$narg = MPI_SUM;\n";
                $args[$#args+1] = "i$narg";
            }
            elsif ($type eq "R")
            {
                print TESTFD "    int i$narg = MPI_REQUEST_NULL;\n";
                $args[$#args+1] = "i$narg";
            }
            elsif ($type eq "E")
            {
                print TESTFD "    int i$narg = MPI_ERRORS_RETURN;\n";
                $args[$#args+1] = "i$narg";
            }
            elsif ($type eq "g")
            {
                print TESTFD "    GUID g$narg = \{ 0x4d36e96e, 0xe325, 0x11c3, \{ 0xbf, 0xc1, 0x08, 0x00, 0x2b, 0xe1, 0x03, 0x18 \} \};\n";
                $args[$#args+1] = "g$narg";
            }
            elsif ($type eq "l")
            {
                print TESTFD "    int i$narg = $narg;\n";
                $args[$#args+1] = "i$narg";
            }
            else
            {
                print STDERR "$filename : error : unrecognized format type $type for $fullformat\n";
            }
        }
        $actargs = $#_ - $genericArgLoc - 1;
        if ($actargs != $narg)
        {
            print STDERR "$filename : error : format $fullformat provides $narg arguments but call has $actargs\n";
        }
        print TESTFD "    err = MPIR_Err_create_code($last_errcode, $fatal_flag, $errclass, $specific_msg";
        foreach my $arg (@args)
        {
            print TESTFD ", $arg";
        }
        print TESTFD ");\n";
        print TESTFD "    ChkMsg( err, $errclass, $specific_msg );\n    }\n";
        # ToDo: pass another string to ChkMsg that contains the
        # names of the variables, as a single string (comma separated).
        # This allows us to review the source of the values for the args.
    }
}

# ==========================================================================
# Read an errnames file.  This allows us to distribute the errnames.txt
# files in the relevant modules, rather than making them part of one
# single master file.
# This updates the global hashs longnames and longnamesDefined
#  ReadErrnamesFile( filename )
# ==========================================================================
sub ReadErrnamesFile {
    my $sourcefile = $_[0];

    open( FD, "<$sourcefile" ) or return 0;
    my $linecount = 0;
    while (<FD>) {
        $linecount++;
        # Skip Comments
        if (/^\s*\#/) { next; }
        # Read entire error message (allow \ at end of line to continue)
        if (/^\s*(\*\*[^:]*):(.*)$/) {
            my $name = $1;
            my $repl = $2;
            $repl =~ s/\r*\n*$//g;
            while ($repl =~ /\\\s*$/) {
                # If there is a \\ at the end, read another.
                # Remove the \ at the end (an alternative is to turn
                # it into a \n (newline), but we may want to avoid
                # multiline messages
                $repl =~ s/\\\s*$//;
                my $inline = <FD>;
                $linecount++;
                $inline =~ s/^\s*//;   # remove leading spaces
                $repl .= $inline;
                $repl =~ s/[\r\n]*$//g; # remove newlines
            }

            # Check that the name and the replacement text at least
            # partially match as to format specifiers
            # (They should have exactly the same pattern, i.e.,
            # if the name has %d %x in is, the replacement should
            # have %d %x, in that order)
            my $namehasformat = ($name =~ /%/);
            my $replhasformat = ($repl =~ /%/);
            if ($namehasformat != $replhasformat) {
                print STDERR "$sourcefile($linecount) : error : format control usage in $name and $repl do not agree\n";
            }
#           if (!defined($longnames{"\"$name\""}))
#           {
#               $longnames{"\"$name\""} = $repl;
#               $longnamesDefined{"\"$name\""} = "$sourcefile:$linecount";
#           }
            # Check that the replacement text doesn't include a unquoted
            # double quote
            if ($repl =~ /(.)\"/) {
                my $prechar = $1;
                if ($1 ne "\\") {
                    print STDERR "$sourcefile($linecount) : error : replacement text for $name contains an unescaped double quote: $repl\n";
                }
            }
            if (!defined($longnames{$name}))
            {
                $longnames{$name} = $repl;
                $longnamesDefined{$name} = "$sourcefile:$linecount";
            }
            else
            {
                print STDERR "$sourcefile($linecount) : warning : attempt to redefine $name.  Duplicate ignored.\n";
            }
        }
    }
    close( FD );
}

# ==========================================================================
# Call this for each file
# This reads a C source or header file and adds does the following:
#   adds any generic message short names encountered to the hash generic_msgs.
#   adds any specific message short names encounter to the hash specific_msgs.
#   adds the filename to the hash generic_loc{msg} as the value (: separated)
#       and the same for hash specific_loc{msg}.
#   The last two are used to provide better error reporting.
#
$filename = "";    # Make global so that other routines can echo filename
sub ProcessFile
{
    # Leave filename global for AddTest
    $filename = $_[0];
    my $linecount = 0;
    open (FD, "<$filename" ) or die "Could not open $filename\n";

    while (<FD>) {
        $linecount++;

        # Skip code that is marked as ignore (e.g., for
        # macros that are used to simplify the use of MPIR_Err_create_code
        # (such macros must also be recognized and processed)
        if (/\/\*\s+--BEGIN ERROR MACROS--\s+\*\//) {
            while (<FD>) {
                $linecount++;
                if (/\/\*\s+--END ERROR MACROS--\s+\*\//) { last; }
            }
            $remainder = "";
            next;
        }

        # Next, remove any comments
        $_ = StripComments( FD, $_ );

        # Skip the definition of the function
        if (/MPI_RESULT\s+MPI[OUR]_Err_create_code/) { $remainder = ""; next; }

        # Match the known routines and macros.
        # Then check that the arguments match if there is a
        # specific string (number of args matches the number present)
        # (MPIU_ERR_FATAL_GET[0-4]?(cond,code,class,gmsg[,smsg,args])
        # Value is a quadruplet of:
        #  1. the count of args where the generic msg begins (starting from 0)
        #  2. location of __LINE__ (-1 for none)
        #  3. specific msg arg required (0 for no, > 0 for yes)
        #  4. only indirect message allowed
        #
        %KnownErrRoutines = ( 'MPIR_Err_create_code'      => '3:-1:1:1',
                              'MPIR_ERRTEST_VALID_HANDLE' => '4:-1:0:1',
                              'MPIU_ERR_FATAL_GET'        => '2:-1:0:1',
                              'MPIU_ERR_GET'              => '1:-1:0:1',
                              'MPIU_ERR_CLASS_GET'        => '2:-1:0:1',
                              'MPIU_ERR_CREATE'           => '1:-1:0:1',

                              'MPIU_E_ERR'                => '0:-1:0:1',
                              );

        while (/(MPI[OUR]_E[A-Za-z0-9_]+)\s*(\(.*)$/) {
            my $routineName = $1;
            my $arglist     = $2;
            if (!defined($KnownErrRoutines{$routineName})) {
                if($routineName =~ /[1-9]$/) {
                    $routineNameN = substr($routineName, 0, $#routineName);
                    if (!defined($KnownErrRoutines{$routineNameN})) {
                        print "Skipping $routineName\n" if $debug;
                        last;
                    }
                    print "Found $routineName, using $routineNameN definition\n" if $debug;
                    $routineName = $routineNameN;
                }
                else {
                    print "Skipping $routineName\n" if $debug;
                    last;
                }
            }
            else {
                print "Found $routineName\n" if $debug;
            }

            my ($genericArgLoc,$hasLine,$hasSpecific,$onlyIndirect) =
                split(/:/,$KnownErrRoutines{$routineName});

            ($leader, $remainder, @args ) = &GetSubArgs( FD, $arglist );
            # Discard leader
            if ($debug) {
                print "Line begins with $leader\n";   # Use $leader to keep -w happy
                foreach $arg (@args) {
                    print "|$arg|\n";
                }
            }
            # Process the signature

            # if signature does not match new function prototype, then skip it
            if ($#args < $genericArgLoc) {
                if (!defined($bad_syntax_in_file{$filename})) {
                    $bad_syntax_in_file{$filename} = 1;
                    print STDERR "$filename($linecount) : error : $routineName call with too few arguments\n";
                }
                next;
            }
            if ($hasLine >= 0 &&
                ($args[$hasLine] ne "__LINE__" && $args[$hasLine] ne "line")) {
                if (!defined($bad_syntax_in_file{$filename})) {
                    $bad_syntax_in_file{$filename} = 1;
                    my $tmpi = $hasLine + 1;
                    print STDERR "$filename($linecount) : error : Expected __LINE__ or line as ${tmpi}th argument of $routineName\n";
                }
                next;
            }

            #my $last_errcode = $args[0];
            #my $fatal_flag = $args[1];
            #my $fcname = $args[2];
            #my $linenum = $args[3];
            #my $errclass = $args[4];
            my $specific_msg = $args[$genericArgLoc];

            if ($specific_msg =~ /(\".*\")/)
            {
                $specific_msg = $1;
            }

            # Check the generic and specific message arguments
            if ($specific_msg =~ /\s"/)
            {
                print STDERR "$filename($linecount) : warning : trailing blank in error key '$specific_msg'\n";
            }

            if ($onlyIndirect && !($specific_msg =~ /^\"\*\*.+\"$/)) {

                print STDERR "$filename($linecount) : error : error key '$specific_msg' has incorrect format\n";
                next;
            }

            if ($specific_msg =~ /%/) {
                # Specific message includes format values.  Check
                # for number and for valid strings if %s
                my $nargs = 0;
                my $tmpmsg = $specific_msg;
                my @stringLocs = ();
                while ($tmpmsg =~ /[^%]*%(.)(.*)/) {
                    $tmpmsg = $2;
                    my $followchar = $1;
                    if ($followchar eq "s") {
                        $stringLocs[$#stringLocs+1] = $nargs;
                    }
                    if ($followchar ne "%") {
                        $nargs ++;
                    }
                    if (! ($followchar =~ /[%xsditpDCRWOEIGFAgl]/) ) {
                        print STDERR "$filename($linecount) : error : Unrecognized format specifier in error key $specific_msg\n";
                    }
                }
                if ($nargs != $#args - $genericArgLoc) {
                    my $actargs = $#args - $genericArgLoc;
                    print STDERR "$filename($linecount) : error : wrong number of arguments for instance error key $specific_msg; expected $nargs but found $actargs\n";
                }
                elsif ($#stringLocs >= 0 && $gStrict) {
                    # Check for reasonable strings if strict checking requested
                    for (my $i=0; $i<=$#stringLocs; $i++) {
                        my $index = $stringLocs[$i];
                        my $string = $args[$genericArgLoc+1+$index];
                        if ($string =~ /\"/) {
                            # Allow a few special cases:
                            # Always: all uppercase and _, single word
                            my $stringOk = 0;
                            if ($string =~ /^\"[A-Z_]*\"$/) {
                                $stringOk = 1;
                            }
                            elsif ($string =~ /^\"\w*\"$/) {
                                if (1) { $stringOk = 1; }
                            }
                            if (!$stringOk) {
                                print STDERR "$filename($linecount) : error : explicit string as argument to error key $specific_msg; explicit string is $string\n";
                            }
                        }
                    }
                }
            }

            if ($build_test_pgm) {
                &AddTestCall( $genericArgLoc, @args )
            }

            if ($specific_msg =~ /^\"(\*\*.*)\"/)
            {
                $specific_msg = $1;
                $specific_msgs{$specific_msg}++;
                $specific_loc{$specific_msg} .= ":$filename($linecount)";
            }
        }
        continue
        {
            $_ = $remainder;
        }
    }
    close FD;
}

# Get all of the .c files from the named directory, including any subdirs
# Also, add any errnames.txt files to the errnamesFiles arrays
sub ExpandDir {
    my $dir = $_[0];
    my @otherdirs = ();
    my @files = ();
    opendir DIR, "$dir";
    while ($filename = readdir DIR) {
        if ($filename =~ /^\./ || $filename eq "CVS" || $filename eq $testfile) {
            next;
        }
        elsif (-d "$dir/$filename") {
            if( ($filename ne "objd")  && ($filename ne "obj") ) {
                $otherdirs[$#otherdirs+1] = "$dir/$filename";
            }
        }
        elsif ($filename =~ /(.*\.[chi][xp]*)$/) {
            # Test for both Unix- and Windows-style directory separators
            if (!defined($skipFiles{"$dir/$filename"}) &&
                !defined($skipFiles{"$dir\\$filename"})) {
                $files[$#files + 1] = "$dir/$filename";
            }
        }
        elsif ($filename eq "errnames.txt") {
            $errnameFiles[$#errnameFiles+1] = "$dir/$filename";
        }
    }
    closedir DIR;
    # (almost) tail recurse on otherdirs (we've closed the directory handle,
    # so we don't need to worry about it anymore)
    foreach $dir (@otherdirs) {
        @files = (@files, &ExpandDir( $dir ) );
    }
    return @files;
}
#
# Other todos:
# It would be good to keep track of any .N MPI_ERR_xxx names in the structured
# comment and match these against any MPI_ERR_yyy used in the code, emitting a
# warning message for MPI_ERR_yyy values used in the code but not mentioned
# in the header.  This could even apply to routines that are not at the MPI
# layer, forcing all routines to document all MPI error classes that they might
# return (this is like requiring routines to document the exceptions that
# they may throw).

